// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RGBCameraAgent.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Vision.Runtime.Cameras
{   
    using System;
    using System.Diagnostics;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization;

    using Microsoft.Robotics.Numerics;
    using Microsoft.Robotics.Runtime;
    using Microsoft.Robotics.Tracing;
    using Microsoft.Robotics.Vision.Cameras;

    /// <summary>
    /// RGB Camera producer agent publishing a RGB Camera message
    /// </summary>
    [DataContract]
    public class RGBCameraAgent : ProducerAgent<RGBCameraAgentMessage>, IControlReceiver<RGBCameraInputMessage>
    {
        /// <summary>
        /// Number of channels for RGB.
        /// </summary>
        private const int NumberOfChannels = 3;

        /// <summary>
        /// Image width in pixels.
        /// </summary>
        [DataMember(Name = "Width")]
        private int width;

        /// <summary>
        /// Image height in pixels.
        /// </summary>
        [DataMember(Name = "Height")]
        private int height;

        /// <summary>
        /// Frame rate denominator in seconds.
        /// </summary>
        [DataMember(Name = "FrameRateDenominator")]
        private int frameRateDenominator;

        /// <summary>
        /// Frame rate numerator in frames.
        /// </summary>
        [DataMember(Name = "FrameRateNumerator")]
        private int frameRateNumerator;

        /// <summary>
        /// Subtype of video stream.
        /// </summary>
        [DataMember(Name = "SubType")]
        private string subType;

        /// <summary>
        /// Friendly name of the camera. Can be expressed as an exact match name or 
        /// a regular expression.
        /// </summary>
        [DataMember(Name = "FriendlyName")]
        private string friendlyName;

        /// <summary>
        /// Camera configurable properties 
        /// </summary>
        [DataMember(Name = "CameraProperties")]
        private RGBCameraProperty[] cameraProperties;

        /// <summary>
        /// Video configurable properties 
        /// </summary>
        [DataMember(Name = "VideoProperties")]
        private RGBVideoProperty[] videoProperties;

        /// <summary>
        /// Optional symbolic link template to disambiguate camera from amongst multiple cameras
        /// </summary>
        [DataMember(Name = "CameraSymbolicLink")]
        private string cameraSymbolicLink;

        /// <summary>
        /// Camera pose
        /// </summary>
        [DataMember(Name = "CameraPose")]
        private Pose cameraPose;

        /// <summary>
        /// Interest patch. Generally refers to a rectangular patch of the image used by downstream components.
        /// </summary>
        [DataMember(Name = "InterestPatch")]
        private Rectangle interestPatch;

        /// <summary>
        /// Reset interval in mSec between resets of camera and video properties. If less
        /// than or equal to zero then properties are never reset.
        /// The reason to reset these is we have noticed that some webcams spontaneously
        /// change some properties after they are set
        /// </summary>
        [DataMember(Name = "PropertyResetIntervalMsec")]
        private long propertyResetIntervalMsec;

        /// <summary>
        /// Underlying RGB Camera
        /// </summary>
        private RGBCameraConnect rgbCameraConnect;

        /// <summary>
        /// Timer for resetting properties
        /// </summary>
        private Stopwatch resetPropertyTimer;

        /// <summary>
        /// Initializes a new instance of the RGBCameraAgent class.
        /// </summary>
        /// <param name="name">Name of the agent.</param>
        /// <param name="width">Width in pixels</param>
        /// <param name="height">Height in pixels</param>
        /// <param name="frameRateNumerator">Frame rate numerator in frames.</param>
        /// <param name="frameRateDenominator">Frame rate denominator in seconds.</param>
        /// <param name="subType">Subtype of video frame.</param>
        /// <param name="friendlyName">Friendly name of USB web cam</param>
        /// <param name="cameraProperties">Array of camera properties to set</param>
        /// <param name="cameraPose">Camera pose</param>
        /// <param name="interestPatch">Patch of interest</param>
        /// <param name="cameraSymbolicLink">Optional symbolic link template to disambiguate camera from amongst multiple cameras </param>
        /// <param name="videoProperties">Array of video properties</param>
        /// <param name="propertyResetIntervalMsec">Property reset interval in mSec. Non positive values mean never reset</param>
        public RGBCameraAgent(
            string name,
            int width,
            int height,
            int frameRateNumerator,
            int frameRateDenominator,
            string subType,
            string friendlyName,
            RGBCameraProperty[] cameraProperties,
            Pose cameraPose,
            Rectangle interestPatch,
            string cameraSymbolicLink = null,
            RGBVideoProperty[] videoProperties = null,
            long propertyResetIntervalMsec = 0)
            : base(name)
        {
            this.width = width;
            this.height = height;
            this.frameRateNumerator = frameRateNumerator;
            this.frameRateDenominator = frameRateDenominator;
            this.subType = subType;
            this.friendlyName = friendlyName;
            this.cameraProperties = cameraProperties;
            this.cameraPose = cameraPose;
            this.interestPatch = interestPatch;

            this.cameraSymbolicLink = cameraSymbolicLink;
            this.videoProperties = videoProperties;
            this.propertyResetIntervalMsec = propertyResetIntervalMsec;
        }

        /// <summary>
        /// Gets the RGB Camera connector.
        /// </summary>
        internal RGBCameraConnect RgbCameraConnect
        {
            get { return this.rgbCameraConnect; }
            private set { this.rgbCameraConnect = value; }
        }

        /// <summary>
        /// Called when the agent is about to start running
        /// </summary>
        public override void OnActivated()
        {
            base.OnActivated();
            this.ActivateCamera();
        }

        /// <summary>
        /// Called when the agent is about to end
        /// </summary>
        public override void OnDeactivated()
        {
            base.OnDeactivated();
            this.DeactivateCamera();
        }

        /// <summary>
        /// Turn the camera on or off depending on the control message.
        /// </summary>
        /// <param name="message">The RGB Camera input message</param>
        public void ReceiveControl(RGBCameraInputMessage message)
        {
            if (message.Enable)
            {                
                if (this.rgbCameraConnect == null)
                {
                    TraceOut.Info(TraceContexts.Vision, "Activating RGB Camera: {0}", this.friendlyName);
                    this.ActivateCamera();
                }
            }
            else
            {
                TraceOut.Info(TraceContexts.Vision, "Dectivating RGB Camera: {0}", this.friendlyName);
                this.DeactivateCamera();
            }            
        }

        /// <summary>
        /// Activate the camera.
        /// </summary>
        private void ActivateCamera()
        {
            this.rgbCameraConnect = new RGBCameraConnect(this.width, this.height, this.frameRateNumerator, this.frameRateDenominator, this.subType, this.friendlyName, this.RGBImageReady, this.cameraSymbolicLink);
            this.rgbCameraConnect.InitializeCamera();
            this.SetAllProperties();
            if (this.propertyResetIntervalMsec > 0)
            {
                this.resetPropertyTimer = new Stopwatch();
                this.resetPropertyTimer.Start();
            }
            else
            {
                this.resetPropertyTimer = null;
            }

            this.rgbCameraConnect.CaptureSample();
        }

        /// <summary>
        /// Set any camera or video properties
        /// </summary>
        private void SetAllProperties()
        {
            this.rgbCameraConnect.SetProperties(this.cameraProperties);
            this.rgbCameraConnect.SetVideoPropertiesWithInitialization(this.videoProperties);
        }

        /// <summary>
        /// Deactivate the camera.
        /// </summary>
        private void DeactivateCamera()
        {
            if (this.rgbCameraConnect != null)
            {
                if (this.rgbCameraConnect.Camera != null)
                {
                    this.rgbCameraConnect.Camera.Shutdown();
                }

                this.rgbCameraConnect.UninitializeCamera();
                this.rgbCameraConnect = null;
            }
        }

        /// <summary>
        /// Convert RGB Image Frames to agent messages and post them to the pipeline.
        /// </summary>
        /// <param name="imageFrame">RGB Image Frame (formatted as Yuy2)</param>
        private void RGBImageReady(ImageFrameYuy2 imageFrame)
        {
            RGBCameraAgentMessage message = new RGBCameraAgentMessage(AgentMessage.GetCurrentTime(), imageFrame, this.cameraPose, this.interestPatch, this.cameraSymbolicLink);
            this.Publisher.Post(message);
            if (null != this.resetPropertyTimer && this.resetPropertyTimer.ElapsedMilliseconds > this.propertyResetIntervalMsec)
            {
                this.SetAllProperties();
                this.resetPropertyTimer.Restart();
            }
        }
    }
}
